
#include "Common.hpp"
#include "GameServer.hpp"

// == Class GameServer =====================================================================================================================

const char GameServer::SymbolTable[2] = {'X', 'O'};
GameServer * GameServer::_instance(0);

GameServer::GameServer(void) : _socket(0), _gameStats(0), _mtx(), _clientThreads()
{
	memset(_gameBoard, '*', BOARD_ROWS * BOARD_COLS);
}

bool GameServer::init(const char * cfgFile)
{
	std::ifstream ifs(cfgFile, std::ios_base::in);

	// Error Check.
	if (ifs.bad() || !ifs.is_open())
	{
		ifs.close();
		LOG_ERROR("Unable To Find Or Open The Server Config File: " << cfgFile);
		return (false);
	}

	char buff[512], str[64];

	ifs.getline(buff, sizeof(buff), '\n');

	// If The First Line Does Not Equals "[T4 Server Conf]", This Is Not A Valid File.
	if (strncmp(buff, "[T4 Server Conf]", 16) != 0)
	{
		ifs.close();
		LOG_ERROR("Invalid Server Config File: " << cfgFile);
		return (false);
	}

	TypeSocket _type;
	int _port = 0, _connections = 0, _num;

	while (!ifs.eof()) // Parse the cfg file:
	{
		ifs.getline(buff, sizeof(buff), '\n');

		if ((*buff == '#') || (*buff == '\0'))
		{
			continue; // Comment Line | Blank Line.
		}
		else if (sscanf(buff, "Port %d", &_num) == 1)
		{
			_port = _num;
		}
		else if (sscanf(buff, "Max Pending Connections %d", &_num) == 1)
		{
			_connections = _num;
		}
		else if (sscanf(buff, "Socket Type %s", str) == 1)
		{
			_type = ((strncmp(str, "NonBlockingSocket", 17) == 0) ? NonBlockingSocket : BlockingSocket);
		}
		else
		{
			continue; // No Valid Commands Read.
		}
	}

	ifs.close();

	try {
		LOG_MSG("Creating Server At Port..: " << _port);
		LOG_MSG("Max Connections..........: " << _connections);
		LOG_MSG("Socket Type..............: " << ((_type == NonBlockingSocket) ? "NonBlockingSocket" : "BlockingSocket"));
		_socket = new SocketServer(_port, _connections, _type);
	}
	catch (...) {

		LOG_MSG("ERROR! Failed To Start Server At Port " << _port);
		return (false);
	}

	LOG_MSG("Game server initialized...");

	return (true);
}

void GameServer::waitForConnections(int maxConnections)
{
	LOG_MSG("Waiting connections...");

	// Player Symbols.
	static int lastSymbol = 0;

	while (maxConnections--)
	{
		Socket * client = 0;

		do { // Keep Waiting For A Connection...
			try {
				client = _socket->Accept();
			}
			catch (...) {
				continue;
			}
		} while (!client);

		LOG_MSG("Got a connection!");
		_clientThreads.push_back(new ClientThread(client, SymbolTable[lastSymbol++], PLAYER_WAITING));
		LOG_MSG("Client thread instantiated...");

		if (lastSymbol >= 2)
			lastSymbol = 0;
	}
}

void GameServer::startGame(void)
{
	LOG_MSG("Start game...");

	unsigned int i = _clientThreads.size();

	while (i--) // Start All Players:
	{
		ClientThread * client = _clientThreads[i];
		client->setPlayerFlags(PLAYER_IN_GAME);
		client->begin();
	}

	_gameStats = PLAYER_IN_GAME;
}

void GameServer::resetGame(void)
{
	_gameStats = 0;

	memset(_gameBoard, '*', BOARD_ROWS * BOARD_COLS);

	unsigned int i = _clientThreads.size();

	if (i > 0)
	{
		while (i--)
			delete _clientThreads[i];

		_clientThreads.clear();
	}
}

int GameServer::checkMyGameState(char player)
{
	int empty = 0;

	if (_gameStats == PLAYER_IN_GAME)
	{
		for (int i = 0; i < 3; i++)
		{
			// Vertical Lines
			if ((_gameBoard[i][0] == player) && (_gameBoard[i][1] == player) && (_gameBoard[i][2] == player))
			{
				_gameStats = PLAYER_WON;
				return (_gameStats);
			}
			// Horizontal Lines
			if ((_gameBoard[0][i] == player) && (_gameBoard[1][i] == player) && (_gameBoard[2][i] == player))
			{
				_gameStats = PLAYER_WON;
				return (_gameStats);
			}
		}

		// Diagonals

		if ((_gameBoard[0][0] == player) && (_gameBoard[1][1] == player) && (_gameBoard[2][2] == player))
		{
			_gameStats = PLAYER_WON;
			return (_gameStats);
		}
		if ((_gameBoard[0][2] == player) && (_gameBoard[1][1] == player) && (_gameBoard[2][0] == player))
		{
			_gameStats = PLAYER_WON;
			return (_gameStats);
		}
	}

	switch (_gameStats)
	{
	case PLAYER_WON:
		{
			// If Gets Here, Than The Game Is Already Over And This Player Lost, So Return That.
			return (PLAYER_LOST);
			break;
		}
	case PLAYER_IN_GAME:
		{
			// Check if The Game Is Draw:
			for (int i = 0; i < BOARD_ROWS; i++)
			{
				for (int j = 0; j < BOARD_COLS; j++)
				{
					if (_gameBoard[i][j] == '*')
						empty++;
				}
			}

			if (!empty)
				_gameStats = PLAYER_DRAW;
			break;
		}
	default:
		{
			break;
		}
	}; // End of switch (_gameStats)

	return (_gameStats);
}

void GameServer::readBoard(char buff[BOARD_ROWS][BOARD_COLS]) const
{
    _mtx.Lock();

	for (int i = 0; i < BOARD_ROWS; i++)
	{
		for (int j = 0; j < BOARD_COLS; j++)
		{
			buff[i][j] = _gameBoard[i][j];
		}
	}

    _mtx.Unlock();
}

void GameServer::readBoardElement(char & elem, int row, int col) const
{
	_mtx.Lock();

	elem = _gameBoard[row][col];

	_mtx.Unlock();
}

void GameServer::writeBoard(const char buff[BOARD_ROWS][BOARD_COLS])
{
	_mtx.Lock();

	for (int i = 0; i < BOARD_ROWS; i++)
	{
		for (int j = 0; j < BOARD_COLS; j++)
		{
			_gameBoard[i][j] = buff[i][j];
		}
	}

	_mtx.Unlock();
}

void GameServer::writeBoardElement(char elem, int row, int col)
{
	_mtx.Lock();

	_gameBoard[row][col] = elem;

	_mtx.Unlock();
}

GameServer::~GameServer(void)
{
	if (_socket)
	{
		_socket->Close();
		delete _socket;
	}

	unsigned int i = _clientThreads.size();

	if (i > 0)
	{
		while (i--)
		{
			delete _clientThreads[i];
		}

		_clientThreads.clear();
	}
}

// == Class ClientThread ===================================================================================================================

char ClientThread::globalSymbol = GameServer::SymbolTable[(rand() + 1) % 1];

ClientThread::ClientThread(void)
: _socket(0), _symbol(0), _playerFlags(0), _terminate_thread_flag(0), _t(0)
{
	LOG_MSG("New Client Thread Instantiated...");
}

ClientThread::ClientThread(Socket * sock, char symbol, int flags)
: _socket(sock), _symbol(symbol), _playerFlags(flags), _terminate_thread_flag(0), _t(0)
{
	LOG_MSG("New Client Thread Instantiated...");
}

void ClientThread::threadProc(void * param)
{
	ClientThread * thread = (ClientThread *)param;
	assert(thread != NULL);

	LOG_MSG("Client Thread Running...");

	char msg[ASCII_MSG_MAX_LEN];

	if (!thread->_socket->ReceiveBytes(msg, ASCII_MSG_MAX_LEN))
	{
		LOG_MSG("Client Is Not Responding! Terminating Thread.");
		return;
	}

	if (strcmp(msg, "T4Client-Connecting") != 0)
	{
		LOG_MSG("Invalid Data Received !");
		return;
	}

	strcpy(msg, "T4Server-Responding");

	if (!thread->_socket->SendBytes(msg, ASCII_MSG_MAX_LEN))
	{
		LOG_MSG("Error Sending Data. Exiting...");
		return;
	}

	GameServer * myServer = GameServer::instance();

	// Send Player Symbol:

	std::string s("Player Symbol = ");
	s.push_back(thread->_symbol);
	strcpy(msg, s.c_str());

	if (!thread->_socket->SendBytes(msg, ASCII_MSG_MAX_LEN))
	{
		LOG_MSG("Error Sending Data. Exiting...");
		return;
	}

	// Send Initial Flags:

	if (!thread->_socket->SendBytes(reinterpret_cast<const char *>(&thread->_playerFlags), sizeof(int)))
	{
		LOG_MSG("Error Sending Data. Exiting...");
		return;
	}

	// Enter The Game Loop:

	ClientMessage inputMessage;
	ServerMessage outputMessage;

	while (!thread->_terminate_thread_flag)
	{
		// Receive Data From The Remote Client.

		if (!thread->_socket->ReceiveBytes(reinterpret_cast<char *>(&inputMessage), sizeof(ClientMessage)))
		{
			LOG_MSG("Client Connection Lost...");
			thread->_terminate_thread_flag = 1;
			break;
		}

		if ((inputMessage.updatedRow >= 0) && (inputMessage.updatedCol >= 0)) // A Negative Number Indicates The Board Did Not Change.
		{
			// Check if is the turn of the player, otherwise, do not accept the new piece
			if (thread->_symbol != globalSymbol)
			{
				myServer->writeBoardElement(thread->_symbol, inputMessage.updatedRow, inputMessage.updatedCol);
				ClientThread::globalSymbol = thread->_symbol;
			}
		}

		// Check If Won, Lost Or Draw...

		outputMessage.playerFlags = myServer->checkMyGameState(thread->_symbol);
		myServer->readBoard(outputMessage.gameBoard);

		// Answer To The Remote Client:

		thread->_socket->SendBytes(reinterpret_cast<const char *>(&outputMessage), sizeof(ServerMessage));

#if defined (DROP_FRAME_RATE)
		SLEEP_MS(COOL_DOWN_TIME);
#endif // DROP_FRAME_RATE
	}

	LOG_MSG("Client Thread Returning...");
}

void ClientThread::setPlayerFlags(int flags)
{
	_playerFlags = flags;
}

int ClientThread::getPlayerFlags(void) const
{
	return (_playerFlags);
}

char ClientThread::getPlayerSymbol(void) const
{
	return (_symbol);
}

const Socket * ClientThread::getSocketPtr(void) const
{
	return (_socket);
}

void ClientThread::begin()
{
	_terminate_thread_flag = 0;
	_t = new Thread(ClientThread::threadProc, this);
}

ClientThread::~ClientThread(void)
{
	_terminate_thread_flag = 1;

	if (_t)
	{
		_t->join();
		delete _t;
	}

	if (_socket)
	{
		_socket->Close();
		delete _socket;
	}

	LOG_MSG("Client Thread Terminated...");
}
